/*
	Copyright (c) 2007, ANYwebcam.com Pty Ltd. All rights reserved.

	The software in this package is published under the terms of the BSD style 
	license, a copy of which has been included with this distribution in the 
	license.txt file.
*/
package com.anywebcam.di.util
{
	import com.anywebcam.di.*;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.utils.getDefinitionByName;

	public class XMLComponentDefinitionsParser extends EventDispatcher implements ComponentDefinitionsParser
	{
		public function XMLComponentDefinitionsParser()
		{
			super();
		}
		
		public function parse( container:DIContainer, data:* ):void
		{
			try
			{
				doParse( container, data );
				dispatchEvent( new Event( Event.COMPLETE ) );
			}
			catch( e:Error )
			{
				// fixme: this is probably a misuse of this event type
				throw e;
				
				dispatchEvent( new IOErrorEvent( IOErrorEvent.IO_ERROR ) );
			}
		}
		
		private function doParse( container:DIContainer, data:* ):void
		{	
			var xml:XML = new XML( data );
			var key:*, value:*, parameters:Array, options:Object;

			var defaultOptions:Object = {};
			defaultOptions.cache = xml.defaults.cache.text() == 'true' ? true : false;
			defaultOptions.injectionType = xml.defaults.injectionType.text() || container.options.injectionType || 'setter';
			
			for each( var componentDefinition:XML in xml.component )
			{
				options 	= container.options || {};
				options.cache = ( componentDefinition.@cache == 'true' ) ? true : false;
				options.injectionType = componentDefinition.@injectionType.length() > 0 ? componentDefinition.@injectionType : defaultOptions.injectionType; /* || container.options.injectionType */
				
				key 		= getLiteralOrClass( componentDefinition.@key, componentDefinition.@keyClass );
				value 		= getLiteralOrClass( componentDefinition.@value, componentDefinition.@valueClass );
				parameters 	= getComponentParameters( componentDefinition, options );
				
				if( !key && value is Class ) key = value;

				container.register( key, value, parameters, options );
			}
		}
		
		private function getComponentParameters( componentDefinition:XML, options:Object ):Array
		{
			if( componentDefinition.parameter.length() == 0 ) return null;
			
			var parameters:Array = [];
			var param:*, paramKey:*, paramValue:*;

			for each( var parameterDefinition:XML in componentDefinition.parameter )
			{
				param = null;
				paramKey = null;
				paramValue = null;
				paramKey = parameterDefinition.@key;
				paramValue = getLiteralOrClass( parameterDefinition.@value, parameterDefinition.@valueClass );

				if( options.injectionType == 'setter' )
				{
					if( !paramKey || ((paramKey is String) && (paramKey as String).length == 0) ) throw new ArgumentError( 'setter injection requires a parameter key for definition:'+ parameterDefinition.toXMLString() );

					param = paramKey;

					if( paramValue )
					{
						param = new Object();
						param[ paramKey ] = paramValue;
					}
				}
				else if( options.injectionType == 'constructor' )
				{
					// fixme: should we allow paramValue to resolve to null ? -- probably yes
					// todo: make throwing errors here an option
					if( !paramValue ) throw new ArgumentError( 'constructor injection requires a non-null value for defintion:'+ parameterDefinition.toXMLString() );

					param = paramValue;

					if( paramKey && (paramKey is String) && (paramKey as String).length > 0 )
					{
						param = new Object();
						param[ paramKey ] = paramValue;
					}
				}

				if( param )
				{
					parameters.push( param );
				}
			}
			
			return parameters;
		}
		
		private function getLiteralOrClass( literal:String, typeName:String ):*
		{
			if( literal ) return literal;
			if( typeName ) return getDefinitionByName( typeName );
			return null;
		}
	}
}